home *** CD-ROM | disk | FTP | other *** search
/ Macwelt 1 / Macwelt DVD 1.toast / Web-Publishing / HTML-Editoren / Alpha ƒ / Tcl / Modes / sasMode.tcl < prev    next >
Encoding:
Text File  |  2001-01-26  |  53.6 KB  |  1,653 lines

  1. ## -*-Tcl-*- (nowrap)
  2.  # ==========================================================================
  3.  #  Statistical Modes - an extension package for Alpha
  4.  # 
  5.  #  FILE: "sasMode.tcl"
  6.  #                                    created: 01/15/00 {07:15:32 pm} 
  7.  #                                last update: 01/26/01 {12:22:11 pm} 
  8.  #  Description: 
  9.  #  
  10.  #  For SAS syntax files.  SAS is not my statistical package of choice. 
  11.  #  Anyone who has access to a newer manual should feel free to update the
  12.  #  list of keywords and send them along to me.
  13.  # 
  14.  #  Author: Craig Barton Upright
  15.  #  E-mail: <cupright@princeton.edu>
  16.  #    mail: Princeton University,  Department of Sociology
  17.  #          Princeton, New Jersey  08544
  18.  #     www: <http://www.princeton.edu/~cupright>
  19.  #  
  20.  # -------------------------------------------------------------------
  21.  #  
  22.  # Copyright (c) 2000-2001  Craig Barton Upright
  23.  # 
  24.  # This program is free software; you can redistribute it and/or modify
  25.  # it under the terms of the GNU General Public License as published by
  26.  # the Free Software Foundation; either version 2 of the License, or
  27.  # (at your option) any later version.
  28.  # 
  29.  # This program is distributed in the hope that it will be useful,
  30.  # but WITHOUT ANY WARRANTY; without even the implied warranty of
  31.  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  32.  # GNU General Public License for more details.
  33.  # 
  34.  # You should have received a copy of the GNU General Public License
  35.  # along with this program; if not, write to the Free Software
  36.  # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  37.  # 
  38.  # ==========================================================================
  39.  ##
  40.  
  41. # ===========================================================================
  42. #
  43. # ◊◊◊◊ Initialization of SAS mode ◊◊◊◊ #
  44.  
  45. alpha::mode SAS 2.1.1 sasMenu {*.sas} {
  46.     sasMenu electricReturn electricTab electricSemicolon electricBraces
  47. } {
  48.     # We require 7.4b21 for prefs handling.
  49.     alpha::package require -loose AlphaTcl 7.4b21
  50.     addMenu sasMenu "SAS" SAS
  51.     set unixMode(sas) {SAS}
  52.     set modeCreator(SaS6) {SAS}
  53. } uninstall {
  54.     this-file
  55. } help {
  56.     file "Statistical Modes Help"
  57. } maintainer {
  58.     "Craig Barton Upright" <cupright@princeton.edu> 
  59.     <http://www.princeton.edu/~cupright/>
  60. }
  61.  
  62. hook::register quitHook SAS::quitHook
  63.  
  64. proc sasMenu {} {}
  65.  
  66. proc sasMode.tcl {} {}
  67.  
  68. namespace eval SAS {}
  69.  
  70. # ===========================================================================
  71. #
  72. # ◊◊◊◊ Setting SAS mode variables ◊◊◊◊ #
  73. #
  74.  
  75. # Removing obsolete preferences from earlier versions.
  76.  
  77. set oldvars {
  78.     addOptions addParameters addProcs addSubProcs autoMark don'tRemindMe
  79.     electricTab funcExpr functionColor indentSlashEndLines keywordColor
  80.     parseExpr procColor subprocColor sasHelp
  81. }
  82.  
  83. foreach oldvar $oldvars {prefs::removeObsolete SASmodeVars($oldvar)}
  84.  
  85. unset oldvar oldvars
  86.  
  87. # ===========================================================================
  88. #
  89. # Standard preferences recognized by various Alpha procs
  90. #
  91.  
  92.  
  93. newPref var  fillColumn         {75}            SAS
  94. newPref var  leftFillColumn     {0}             SAS
  95. newPref var  prefixString       {* }            SAS
  96. newPref var  wordBreak          {[a-zA-Z0-9]+}  SAS
  97. newPref var  wordBreakPreface   {[^-a-zA-Z0-9]} SAS
  98. newPref flag wordWrap           {0}             SAS
  99.  
  100. # ===========================================================================
  101. #
  102. # Flag preferences
  103. #
  104.  
  105. newPref flag autoMark           {0}     SAS     {SAS::rebuildMenu markSasFileAs}
  106.  
  107. # Indent all continued commands, indicated by the lack of a semi-colon at
  108. # the end of a line, by the full indentation amount rather than half.
  109. newPref flag fullIndent         {1}     SAS     {SAS::rebuildMenu markSasFileAs}
  110.  
  111. # By default command double-click will send a command to on-line help, and
  112. # option double-click sends a command to the local SAS application. 
  113. # Check this box to switch these key combinations.
  114. newPref flag localHelp          {0}     SAS     {SAS::rebuildMenu sasHelp}
  115.  
  116. # Check this box if your keyboard does not have a "Help" key.  This will
  117. # change some of the menu's key bindings.
  118. newPref flag noHelpKey          {0}     SAS     {SAS::rebuildMenu sasHelp}
  119.  
  120. # Set the list of flag preferences which can be changed in the menu.
  121.  
  122. set SASPrefsInMenu [list        \
  123.   "localHelp"                   \
  124.   "noHelpKey"                   \
  125.   "fullIndent"                  \
  126.   ]
  127.  
  128. # ===========================================================================
  129. #
  130. # Variable preferences
  131.  
  132. # Enter additional arguments to be colorized. 
  133. newPref var addArguments        {}      SAS     {SAS::colorizeSAS}
  134.  
  135. # Enter additional SAS proc commands to be colorized.  
  136. newPref var addCommands         {}      SAS     {SAS::colorizeSAS}
  137.  
  138. # Command double-clicking on a SAS keyword will send it to this url for a
  139. # help reference page.
  140. newPref url helpUrl             {}      SAS
  141.  
  142. # The "SAS Home Page" menu item will send this url to your browser.
  143. newPref url sasHomePage         {http://www.sas.com/}   SAS
  144.  
  145. # Click on "Set" to find the local SAS application.
  146. newPref sig sasSig              {SaS6}  SAS
  147.  
  148. # ===========================================================================
  149. #
  150. # Color preferences
  151. #
  152. # Nomenclature notes:
  153. # SAS seems to have five levels of possible keywords.
  154. #   1. the top level "proc" specification:  anova, freq varcomp
  155. #   2. sub-level procs (or "subprocs"):  rename, value, range
  156. #   2. "arguments", which require no parameters:  ls, missover, in1
  157. #   3. "options", which require parameters:  converge, data, gamma
  158. #   4. "parameters", preset as opposed to user supplied: full, part  
  159. #
  160. # The default setup of this mode is to colorize all of procs and subprocs
  161. # blue; arguments, options, and parameters are magenta.  The user does not
  162. # have to specify all of these different levels -- only Argument, Command,
  163. # Comment, String, and Symbol colors appear in the preferences.
  164. # Of these four statistical packages, I am the most unfamiliar with SAS.
  165. # This mode is my good-hearted attempt, but is distributed with no
  166. # assurances that it is complete.
  167.  
  168. # See the Statistical Modes Help file for an explanation of these different
  169. # categories, and lists of keywords.
  170. newPref color argumentColor     {magenta}   SAS     {SAS::colorizeSAS}
  171. newPref color commandColor      {blue}      SAS     {SAS::colorizeSAS}
  172. newPref color commentColor      {red}       SAS     {stringColorProc}
  173. newPref color stringColor       {green}     SAS     {stringColorProc}
  174.  
  175. # The color of symbols such as "/", "@", etc.
  176. newPref color symbolColor       {magenta}   SAS     {SAS::colorizeSAS}
  177.  
  178. regModeKeywords -e {*} -b {/*} {*/}     \
  179.   -c $SASmodeVars(commentColor)         \
  180.   -s $SASmodeVars(stringColor) SAS {} 
  181.  
  182. # ===========================================================================
  183. # Flag Flip
  184. #
  185. # Called by menu items, change the value of flag preferences.
  186.  
  187. proc SAS::flagFlip {pref} {
  188.  
  189.     global mode SASmodeVars
  190.  
  191.     set SASmodeVars($pref) [expr {$SASmodeVars($pref) ? 0 : 1}]
  192.     set oldMode $mode
  193.     set mode "SAS"
  194.     synchroniseModeVar $pref $SASmodeVars($pref)
  195.     set mode $oldMode
  196.     if {$SASmodeVars($pref)} {
  197.         set end "on"
  198.     } else {
  199.         set end "off"
  200.     }
  201.     message "The \"$pref\" preference is now $end."
  202. }
  203.  
  204. # ===========================================================================
  205. # Comment Character variables for Comment Line / Paragraph / Box menu items.
  206.  
  207. set SAS::commentCharacters(General)     "* "
  208. set SAS::commentCharacters(Paragraph)   [list "/* " " */" " * "]
  209. set SAS::commentCharacters(Box)         [list "/*" 2 "*/" 2 "*" 3]
  210.  
  211. # The Comment Line command is hard-wired -- except for the C and C++ modes,
  212. # if the commentCharacters(Paragraph) are different, then Comment Line will
  213. # automatically be bracketed.  Thus I am simply redefining the command-d
  214. # key-binding to ignore commentLine
  215.  
  216. Bind 'd' <c>  {insertPrefix} SAS
  217.  
  218. # ===========================================================================
  219. #
  220. # ◊◊◊◊ Keyword Dictionaries ◊◊◊◊ #
  221. #
  222.  
  223. # Making sure that SASUserCommands and SASUserArguments exist.
  224. # These will be over-ridden if they are loaded from a ${mode}Prefs.tcl file.
  225. #
  226.  
  227. set SASUserCommands     ""
  228. set SASUserArguments    ""
  229.  
  230. # ===========================================================================
  231. #
  232. # ◊◊◊◊   SAS Commands   ◊◊◊◊ #
  233. #
  234.  
  235. set SASCommands {
  236.     aceclus anova calis cancorr candisc catmod cluster corresp discrim
  237.     factor fastclus format freq genmod glm glmmod inbreed kde krige2d
  238.     lattice lifereg lifetest loess logistic mds mixed modeclus multtest
  239.     nested nlin nlmixed npar1way orthoreg phreg plan pls princomp prinqual
  240.     probit proc reg rsreg score stepdisc surveymeans surveyreg surveyselect
  241.     tpspline transreg tree ttest varclus varcomp variogram
  242. }
  243.  
  244. # ===========================================================================
  245. #
  246. # ◊◊◊◊   SAS Subprocs   ◊◊◊◊ #
  247. #
  248.  
  249. set SASSubprocs {
  250.     arima array autoreg average axis1 axis2 by cards centroid choro "class"
  251.     col colblock colcopy collist colpct cols column columns complete
  252.     computab contents control data datasource density dftest do end
  253.     endogenous estimate exogenous filename fit footnote1 footnote2
  254.     footnote3 footnote4 forecast form gmap goptions gplot id identify
  255.     infile input instruments keep label lagged last legend legend1 legend2
  256.     length let libname macro merge model monthly options output parms
  257.     pattern1 pattern2 pattern3 pattern4 pattern5 pattern6 pattern7 pattern8
  258.     plot print put quarterly quit range rename restrict retain return row
  259.     rowblock rowcopy rowlist rows run select set solve sort sumby symbol
  260.     symbol1 symbol2 symbol3 symbol3 symbol4 tables threshold title title1
  261.     title2 title3 title4 value var weights where
  262. }
  263.  
  264. # ===========================================================================
  265. #
  266. # ◊◊◊◊   SAS Arguments   ◊◊◊◊ #
  267. #
  268.  
  269. set SASArguments {
  270.     _col_ _row_ all b bcorr bcov bsscp bvreg c cback clogit clogits colors
  271.     corr corrb dbname device distance eof f garch gr2 h haxis hpos href i
  272.     in1 int intercept interval j joint l lead logit logits lrecl ls lsd
  273.     lspace map marginal marginals maxit maxiter mean means method
  274.     "missover" mpsprt mySAS nodesign nogls noint noiter noparm noprint
  275.     noprofile noresponse notrans noun obs oneway outall outby outcont
  276.     outest pcorr pcov pp printout proby psscp qq red redundancy regwf regwq
  277.     response s scjeffe seb short short shortanova sidak simple smc smm snk
  278.     spcorr sqpcorr sqspcorr stb stdmean survey t tcorr tcov trend tsscp
  279.     tukey type v vdep vpos w waller wcorr wcov wdep wsscp wteg
  280. }
  281.  
  282. # ===========================================================================
  283. #
  284. # ◊◊◊◊   SAS Options   ◊◊◊◊ #
  285. #
  286.  
  287. set SASOptions {
  288.     absolute absorb archtest border converge crosscorr diagonal dif dwprob
  289.     filetype frame from identity initial intnx log manova maxiter metric
  290.     mulripass nlag nlags noconstant noobs noprint ourstat out outfull
  291.     outselect outstat overlay partial prefix rannor sing singular to weight
  292.     xlog
  293. }
  294.     
  295.  
  296. # ===========================================================================
  297. #
  298. # ◊◊◊◊   SAS Parameters   ◊◊◊◊ #
  299. #
  300.  
  301. set SASParameters {
  302.     absolute absorb and asmc average biweight centroid circle complete
  303.     converge density diagonal else eml epanechnikov equamax flexible
  304.     formatted full identity if if in initial internal join kj manova max
  305.     maxiter mcquitty median metric multipass needle no none normal npar one
  306.     orthmox ourstat out outstat p percent plus prefix procustes promax
  307.     proportion qtrvars quarimax random sing single singular smc sorted
  308.     special spline splines star test then triweight twostage uniform
  309.     varimax ward weight yes
  310. }
  311.  
  312. # ===========================================================================
  313. # Colorize SAS.
  314. # Set all keyword lists, and colorize.
  315. # Could also be called in a <mode>Prefs.tcl file
  316.  
  317. proc SAS::colorizeSAS {{pref ""}} {
  318.     
  319.     global SASmodeVars SASCommands   SASSubprocs     SASArguments 
  320.     global SASOptions  SASParameters SASUserCommands SASUserArguments
  321.     
  322.     global SASlowerCaseCmds SASbothCaseCommands SASArgumentsList SAScmds
  323.     
  324.     # Procs and Subprocs only, for SAS::Completion::Command.
  325.     set SASlowerCaseCommands [concat \
  326.       $SASCommands $SASSubprocs $SASmodeVars(addCommands) $SASUserCommands]
  327.     message "Creating ALL CAP commands for SAS mode …"
  328.     set SASupperCaseCommands [string toupper $SASlowerCaseCommands]
  329.  
  330.     set SASbothCaseCommands [lsort [concat \
  331.       $SASlowerCaseCommands $SASupperCaseCommands]]
  332.     # Arguments, Options, Parameters
  333.     set SASArgumentsList [concat \
  334.       $SASArguments  $SASmodeVars(addArguments) $SASOptions \
  335.       $SASParameters $SASUserArguments]
  336.     
  337.     # Then, create the list of all keywords for completions.  SAS
  338.     # keywords are not case-sensitive.  To allow for different user
  339.     # styles, we'll include lower case commands as well as ALL CAPS.
  340.     set SASlowerCaseCmds [concat \
  341.       $SASlowerCaseCommands $SASArgumentsList]
  342.     
  343.     set SASupperCaseCmds [string toupper $SASlowerCaseCmds]
  344.  
  345.     set SAScmds [lsort [lunique [concat \
  346.       $SASlowerCaseCmds $SASupperCaseCmds]]]
  347.     message ""
  348.     
  349.     # Commands
  350.     regModeKeywords -a                                              \
  351.       -k $SASmodeVars(commandColor) SAS $SASbothCaseCommands 
  352.     
  353.     # Arguments, Options, Parameters
  354.     regModeKeywords -a                                              \
  355.       -k $SASmodeVars(argumentColor) SAS $SASArgumentsList
  356.     
  357.     # Symbols
  358.     regModeKeywords -a                                              \
  359.       -k $SASmodeVars(symbolColor) SAS {|}                          \
  360.       -i "+" -i "-" -i "_" -i "\\"                                  \
  361.       -I $SASmodeVars(symbolColor)
  362.  
  363.     if {$pref != ""} {refresh}
  364. }
  365.  
  366. # Call this now.
  367.  
  368. SAS::colorizeSAS
  369.  
  370. # ===========================================================================
  371. #
  372. # Reload Completions.
  373. # This is now an obsolete proc.
  374.  
  375. proc SAS::reloadCompletions {} {
  376.     alertnote "\"SAS::reloadCompletions\" is an obsolete proc.\
  377.       It should be removed from your SASPrefs.tcl file."
  378. }
  379.  
  380. # ===========================================================================
  381. #
  382. # ◊◊◊◊ Key Bindings, Electrics ◊◊◊◊ #
  383. # abbreviations:  <o> = option, <z> = control, <s> = shift, <c> = command
  384.  
  385. # Known bug: Key-bindings from other global menus might conflict with those
  386. # defined in the SAS menu.  This will help ensure that this doesn't happen.
  387.  
  388. Bind 's'    <cs>    {SAS::switchToSas} SAS
  389. Bind 'p'    <cs>    {SAS::processFile} SAS
  390. Bind 'p'    <csz>   {SAS::processSelection} SAS
  391. Bind 'p'    <cs>    {SAS::insertPath} SAS
  392.  
  393. Bind 'n'    <sz>    {SAS::nextCommand} SAS
  394. Bind 'p'    <sz>    {SAS::prevCommand} SAS
  395. Bind 's'    <sz>    {SAS::selectCommand} SAS
  396. Bind 'c'    <sz>    {SAS::copyCommand} SAS
  397.  
  398. Bind 'i'    <cz>    {SAS::reformatCommand} SAS
  399.  
  400. Bind '\)'           {SAS::electricRight "\)"} SAS
  401.  
  402. # For those that would rather use arrow keys to navigate.  Up and down
  403. # arrow keys will advance to next/prev command, right and left will also
  404. # set the cursor to the top of the window.
  405.  
  406. Bind    up  <sz>    {SAS::prevCommand 0 0} SAS
  407. Bind  left  <sz>    {SAS::prevCommand 0 1} SAS
  408. Bind  down  <sz>    {SAS::nextCommand 0 0} SAS
  409. Bind right  <sz>    {SAS::nextCommand 0 1} SAS
  410.  
  411. # ===========================================================================
  412. # SAS Electric Semi
  413. # Inserts a semi, carriage return, and indents properly.
  414.  
  415. proc SAS::electricSemi {} {
  416.     
  417.     if {[literalChar]} {
  418.         typeText {;}
  419.     } else {
  420.         typeText {;}
  421.         bind::CarriageReturn
  422.     }
  423. }
  424.  
  425. # ===========================================================================
  426. # SAS Carriage Return
  427. # Inserts a carriage return, and indents properly.
  428.  
  429. proc SAS::carriageReturn {} {
  430.     
  431.     global SASmodeVars
  432.     
  433.     if {[isSelection]} {
  434.         deleteSelection
  435.     } 
  436.     set pos1 [lineStart [getPos]]
  437.     set pos2 [getPos]
  438.     if {[regexp {^([\t ])*(\}|\))} [getText $pos1 $pos2]]} {
  439.         createTMark temp $pos2
  440.         catch {bind::IndentLine}
  441.         gotoTMark temp ; removeTMark temp
  442.     } 
  443.     insertText "\r"
  444.     catch {bind::IndentLine}
  445. }
  446.  
  447. # ===========================================================================
  448. #
  449. # SAS Electric Left, Right
  450. # Adapted from "tclMode.tcl"
  451.  
  452. proc SAS::electricLeft {} {
  453.  
  454.     if {[literalChar]} {
  455.         typeText "\{"
  456.         return
  457.     }
  458.     set pat "\}\[ \t\r\n\]*(else(if)?)\[ \t\r\n\]*\$"
  459.     set pos [getPos]
  460.     if { [set result [findPatJustBefore "\}" $pat $pos word]] == "" } { 
  461.         insertText "\{"
  462.         return
  463.     }
  464.     # we have an if/else(if)/else
  465.     switch -- $word {
  466.         "else" {
  467.             deleteText [lindex $result 0] $pos
  468.             elec::Insertion "\} $word \{\r\t••\r\}\r••"
  469.         }
  470.         "elseif" {
  471.             deleteText [lindex $result 0] $pos
  472.             elec::Insertion "\} $word \{••\} \{\r\t••\r\}\r••"
  473.         }
  474.     }
  475. }
  476.  
  477. proc SAS::electricRight {{char "\}"}} {
  478.     
  479.     if {[literalChar]} {
  480.         typeText $char
  481.         return
  482.     }
  483.     set pos [getPos]
  484.     typeText $char
  485.     if {![regexp {[^ \t]} [getText [lineStart $pos] $pos]]} {
  486.         set pos [lineStart $pos]
  487.         createTMark temp [getPos]
  488.         catch {bind::IndentLine}
  489.         gotoTMark temp ; removeTMark temp
  490.         bind::CarriageReturn
  491.     } 
  492.     if {[catch {blink [matchIt $char [pos::math $pos - 1]]}]} {
  493.         beep ; message "No matching $char !!"
  494.     } 
  495. }
  496.  
  497. # ===========================================================================
  498. #
  499. # ◊◊◊◊ Indentation ◊◊◊◊ #
  500. # SAS::correctIndentation is necessary for Smart Paste, and returns the
  501. # correct level of indentation for the current line.  SAS::indentLine uses
  502. # this level to indent the current line.
  503. # We have two level of indentation in SAS, for the continuation of
  504. # commands, in which case we simply indent to the amount of the SAS mode
  505. # variable indentationAmount, and for nexted braces.
  506. # In SAS::correctIndentation, we grab the previous non-commented line,
  507. # remove all of the characters besides braces and quotes, and then convert
  508. # it all to a list to be evaluated.  Braces contained within quotes, as
  509. # well as literal characters, should all be ignored and the remaining
  510. # braces are used to determine the correct level of nesting.
  511.  
  512. proc SAS::indentLine {{pos ""}} {
  513.     
  514.     if {$pos == ""} {set pos [getPos]} 
  515.     # Get details of current line.
  516.     set posBeg [lineStart [getPos]]
  517.     set text [getText $posBeg [nextLineStart $posBeg]]
  518.     regexp {^[ \t]*} $text white 
  519.     set posNext1 [pos::math $posBeg + [string length $white]]
  520.     set posNext2 [pos::math $posNext1 + 1]
  521.     if {[pos::compare $posNext2 > [maxPos]]} {
  522.         set posNext2 [maxPos]
  523.     } 
  524.     # Determine the correct level of indentation for this line, given the
  525.     # next character.
  526.     set lwhite [SAS::correctIndentation $pos [getText $posNext1 $posNext2]]
  527.     set lwhite [text::indentOf $lwhite]
  528.     if {$white != $lwhite} {
  529.         replaceText $posBeg $posNext1 $lwhite
  530.     }
  531.     goto [pos::math $posBeg + [string length $lwhite]]
  532. }
  533.  
  534. proc SAS::correctIndentation {pos {next ""}} {
  535.     
  536.     global mode indent_amounts SASmodeVars
  537.     
  538.     if {$mode == "SAS"} {
  539.         set continueIndent [expr {$SASmodeVars(fullIndent) + 1}]
  540.     } else {
  541.         set continueIndent 2
  542.     } 
  543.     
  544.     set posBeg   [lineStart $pos]
  545.     # Get information about this line, previous line ...
  546.     set thisLine  [SAS::getCommandLine $posBeg 1 1]
  547.     set prevLine1 [SAS::getCommandLine [pos::math $posBeg - 1] 0 1]
  548.     set prevLine2 [SAS::getCommandLine [pos::math [lindex $prevLine1 0] - 1] 0 1]
  549.     set lwhite    [lindex $prevLine1 1]
  550.     # If we have a previous line ...
  551.     if {[pos::compare [lindex $prevLine1 0] != $posBeg]} {
  552.         set pL1 [string trim [lindex $prevLine1 2]]
  553.         # Indent if the last line did not terminate the command.
  554.         if {![regexp {;([\t ]?)$} $pL1]} {
  555.             incr lwhite $indent_amounts($continueIndent)
  556.         } 
  557.         # Check to make sure that the previous command was not itself a
  558.         # continuation of the line before it.
  559.         if {[pos::compare [lindex $prevLine1 0] != [lindex $prevLine2 0]]} {
  560.             set pL2 [string trim [lindex $prevLine2 2]]
  561.             if {![regexp {;([\t ]?)$} $pL2]} {
  562.                 incr lwhite $indent_amounts(-$continueIndent)
  563.             } 
  564.         }
  565.         # Find out if there are any unbalanced {,},(,) in the last line.
  566.         regsub -all {[^ \{\}\(\)\"\*\/\\]} $pL1 { } line
  567.         # Remove all literals.
  568.         regsub -all {\\\{|\\\}|\\\(|\\\)|\\\"|\\\*|\\\/} $line { } line
  569.         regsub -all {\\} $line { } line
  570.         # Remove everything surrounded by quotes.
  571.         regsub -all {\"([^\"]+)\"} $line { } line
  572.         regsub -all {\"} $line { } line
  573.         # Remove everything surrounded by bracketed comments.
  574.         regsub -all {/\*([^\*/]+)\*/} $line { } line
  575.         # Now turn all braces into 2's and -2's
  576.         regsub -all {\{|\(} $line { 2 }  line
  577.         regsub -all {\}|\)} $line { -2 } line
  578.         # This list should now only contain 2's and -2's.
  579.         foreach i $line {
  580.             if {$i == "2" || $i == "-2"} {incr lwhite $indent_amounts($i)} 
  581.         }
  582.         # Did the last line start with a lone \) or \} ?  If so, we want to
  583.         # keep the indent, and not make call it an unbalanced line.
  584.         if {[regexp {^[\t ]*(\}|\))} $pL1]} {
  585.             incr lwhite $indent_amounts(2)
  586.         } 
  587.     } 
  588.     # If we have a current line ...
  589.     if {[pos::compare [lindex $thisLine 0] == $posBeg]} {
  590.         # Reduce the indent if the first non-whitespace character of this
  591.         # line is ) or \}.
  592.         set tL [lindex $thisLine 2]
  593.         if {$next == "\}" || $next == ")" || [regexp {^[\t ]*(\}|\))} $tL]} {
  594.             incr lwhite $indent_amounts(-2)
  595.         } 
  596.     } 
  597.     # Now we return the level to the calling proc.
  598.     return [expr {$lwhite > 0 ? $lwhite : 0}]
  599. }
  600.  
  601. # ===========================================================================
  602. # Get Command Line
  603. # Find the next/prev command line relative to a given position, and return
  604. # the position in which it starts, its indentation, and the complete text
  605. # of the command line.  If the search for the next/prev command fails,
  606. # return an indentation level of 0.
  607.  
  608. proc SAS::getCommandLine {pos {direction 1} {ignoreComments 1}} {
  609.     
  610.     if {$ignoreComments} {
  611.         set pat {^[\t ]*[^\t\r\n\*/ ]}
  612.     } else {
  613.         set pat {^[\t ]*[^\t\r\n ]}
  614.     } 
  615.     set posBeg [pos::math [lineStart $pos] - 1]
  616.     if {[pos::compare $posBeg < [minPos]]} {
  617.         set posBeg [minPos]
  618.     } 
  619.     set lwhite 0
  620.     if {![catch {search -f $direction -r 1 $pat $pos} match]} {
  621.         set posBeg [lindex $match 0]
  622.         set lwhite [posX [pos::math [lindex $match 1] - 1]]
  623.     }
  624.     set posEnd [pos::math [nextLineStart $posBeg] - 1]
  625.     if {[pos::compare $posEnd > [maxPos]]} {
  626.         set posEnd [maxPos]
  627.     } 
  628.     return [list $posBeg $lwhite [getText $posBeg $posEnd]]
  629. }
  630.  
  631. # ===========================================================================
  632. # ◊◊◊◊ Command Double Click ◊◊◊◊ #
  633. #
  634. # Checks to see if the highlighted word appears in any keyword list, and if
  635. # so, sends the selected word to the http://search.sas.com/ help site.
  636. # (Default preference is not the most useful site, but the best I could find.)
  637. #
  638. # Control-Command double click will insert syntax information in status bar.
  639. # Shift-Command double click will insert commented syntax information in window.
  640. # (The above is not yet implemented -- need to enter all of the syntax info.)
  641.  
  642. proc SAS::DblClick {from to shift option control} {
  643.     
  644.     global SASmodeVars SAScmds SASSyntaxMessage
  645.         
  646.     select $from $to
  647.     set command [getSelect]
  648.     
  649.     if {[lsearch -exact $SAScmds $command] == -1} {
  650.         message "\"$command\" is not defined as a SAS system keyword."
  651.         return
  652.     }
  653.     # Defined as a keyword, determine if there's a syntax message.
  654.     # Any modifiers pressed?
  655.     if {$control} {
  656.         # CONTROL -- Just put syntax message in status bar window
  657.         if {[info exists SASSyntaxMessage($command)]} {
  658.             message $SASSyntaxMessage($command)        
  659.         } else {
  660.             message "Sorry, no syntax information available for $command"
  661.         } 
  662.     } elseif {$shift} {
  663.         # SHIFT --Just insert syntax message as commented text
  664.         if {[info exists SASSyntaxMessage($command)]} {
  665.             endOfLine
  666.             insertText "\r"
  667.             insertText "$SASSyntaxMessage($command)"
  668.             comment::Line
  669.         } else {
  670.             message "Sorry, no syntax information available for $command"
  671.         } 
  672.     } elseif {$option && !$SASmodeVars(localHelp)} {
  673.         # Now we have four possibilities, based on "option" key and the
  674.         # preference for "local Help Only".  (Local Help Only actually
  675.         # switches the "normal" behavior of options versus not.)
  676.         # 
  677.         # OPTION, local help isn't checked -- Send command to local application
  678.         SAS::localCommandHelp $command
  679.     } elseif {$option && $SASmodeVars(localHelp)} {
  680.         # OPTION, but local help is checked -- Send command for on-line help.
  681.         SAS::wwwCommandHelp $command
  682.     } elseif {$SASmodeVars(localHelp)} {
  683.         # No modifiers, local help is checked -- Send command to local app.
  684.         SAS::localCommandHelp $command
  685.     } else {
  686.         # No modifiers, no local help checked -- Send command for on-line
  687.         # help.  This is the "default" behavior.
  688.         SAS::wwwCommandHelp $command
  689.     }
  690. }
  691.  
  692. # ===========================================================================
  693. # WWW Command Help
  694. # Send command to defined url, prompting for text if necessary.
  695.  
  696. proc SAS::wwwCommandHelp {{command ""}} {
  697.     
  698.     global SASmodeVars
  699.     
  700.     if {$command == ""} {
  701.         set command [prompt "on-line SAS help for ... " [getSelect]] 
  702.         # set command [statusPrompt "on-line help for ... " ] 
  703.     } 
  704.     message "\"$command\" sent to $SASmodeVars(helpUrl)"
  705.     icURL $SASmodeVars(helpUrl)$command
  706. }
  707.  
  708. # ===========================================================================
  709. # Local Command Help
  710. # Send command to local application, prompting for text if necessary.
  711.  
  712. proc SAS::localCommandHelp {{command ""} {app "SAS"}} {
  713.     
  714.     SAS::betaMessage
  715.     
  716.     global SASmodeVars tcl_platform
  717.     
  718.     if {$command == ""} {
  719.         set command [prompt "local $app application help for ... " [getSelect]]
  720.         # set command [statusPrompt "local $app application help for ..." ]
  721.     }
  722.     set pf $tcl_platform(platform)
  723.     
  724.     # We have three possible options here, based on platform.
  725.     
  726.     if {$pf == "macintosh"} {
  727.         # Make sure that the Macintosh application for the signature exists.
  728.         if {[catch {[nameFromAppl [SAS::sig]]}]} {
  729.             SAS::selectApplication $app
  730.         } 
  731.     } elseif {$pf == "windows" || $pf == "unix"} {
  732.         # Make sure that the Windows application for the signature exists. 
  733.         # We assume that this will work for unix, too.
  734.         if {![file exists [S::sig]]} {
  735.             SAS::selectApplication $app
  736.         } 
  737.     } 
  738.     # Now we actually do something ...
  739. }
  740.  
  741.  
  742. # ===========================================================================
  743. #
  744. # ◊◊◊◊ Mark File and Parse Functions ◊◊◊◊ #
  745. #
  746.  
  747. # ===========================================================================
  748. #
  749. # SAS Mark File
  750. # This will return the first 35 characters from the first non-commented
  751. # word that appears in column 0.  All other output files (those not
  752. # recognized) will take into account the additional left margin elements
  753. # added by SAS.
  754. #
  755.  
  756. proc SAS::MarkFile {{type ""}} {
  757.     
  758.     removeAllMarks
  759.     
  760.     message "Marking File …"
  761.     
  762.     set pos [minPos]
  763.     set count 0
  764.     # Figure out what type of file this is -- source, or output.
  765.     # The variable "type" refers to a call from the SAS menu.
  766.     # Otherwise we try to figure out the type based on the file's suffix.
  767.     if {$type == ""} {
  768.         if {[win::CurrentTail] == "* SAS Mode Example *"} {
  769.             # Special case for Mode Examples, but only if called from
  770.             # Marks menu.  (Called from SAS menu, "type" will over-ride.
  771.             set type  ".sas"
  772.         } else {
  773.             set type [file extension [win::CurrentTail]]
  774.         }
  775.     }
  776.     # Now set the mark regexp.
  777.     if {$type == ".sas" } {
  778.         # Source file.
  779.         set markExpr {^(\*\*\*[ ]|\*\*\*\*[ ])?[a-zA-Z0-9]}
  780.     } else {
  781.         # None of the above, so assume that it's output
  782.         set markExpr {^([0-9]+((        )|(         )))+(\*\*\*[ ]|\*\*\*\*[ ])?[a-zA-Z0-9]}
  783.     }
  784.     # Mark the file
  785.     while {![catch {search -s -f 1 -r 1 -m 0 -i 1 $markExpr $pos} match]} {
  786.         incr count 
  787.         set posBeg [lindex $match 0]
  788.         set posEnd [nextLineStart $posBeg]
  789.         if {[pos::compare $posEnd > [maxPos]]} {set posEnd [maxPos]} 
  790.         set line   [string trimright [getText $posBeg $posEnd]]
  791.         # Get rid of the leading "[0-9]  " for output files
  792.         regsub {^[0-9]*[0-9]*[0-9]*[0-9]} $line {} line
  793.         # Get rid of braces.
  794.         regsub -all {\{|\[} $line {(} line
  795.         regsub -all {\}|\]} $line {)} line
  796.         set line [string trimleft  $line " "]
  797.         set line  "  $line"
  798.         if {[regsub {  \*\*\*\* } $line {* } line]} {
  799.             incr count -1
  800.         } elseif {[regsub {  \*\*\* } $line {• } line]} {
  801.             incr count -1
  802.         } 
  803.         if {[string length $line] > 35} {
  804.             set line "[string range $line 0 35] ..."
  805.         } else {
  806.             # Get rid of trailing semi-colons.
  807.             set line  [string trimright $line ";" ]
  808.         }
  809.         # If the mark starts with "run", ignore it.
  810.         if {![regexp {^  (run|RUN)} $line]} {
  811.             setNamedMark $line $posBeg $posBeg $posBeg
  812.         } 
  813.         set pos $posEnd
  814.     }
  815.     message "This file contains $count commands."
  816. }
  817.  
  818. # ===========================================================================
  819. #
  820. # SAS Parse Functions
  821. # This will return only the SAS command names.
  822.  
  823. proc SAS::parseFuncs {} {
  824.     
  825.     global sortFuncsMenu    
  826.     
  827.     set pos [minPos]
  828.     set m {}
  829.     while {[set match [search -s -f 1 -r 1 -i 0 -n {^(\w+)} $pos]] != ""} {
  830.         if {[regexp -- {^(\w+)} [eval getText $match] "" word]} {
  831.             lappend m [list $word [lindex $match 0]]
  832.         }
  833.         set pos [lindex $match 1]
  834.     }
  835.     if {$sortFuncsMenu} {
  836.         regsub -all "\[\{\}\]" [lsort -ignore $m] "" m
  837.     } else {
  838.         regsub -all "\[\{\}\]" $m "" m
  839.     }   
  840.     return  $m
  841. }
  842.  
  843. # ===========================================================================
  844. # ◊◊◊◊ -------------------- ◊◊◊◊ #
  845. # ◊◊◊◊ SAS Menu ◊◊◊◊ #
  846. # based upon the Stata menu, contributed by 
  847. # L. Phillip Schumm <pschumm@uchicago.edu>
  848.  
  849. # Tell Alpha what procedures to use to build all menus, submenus.
  850.  
  851. menu::buildProc sasMenu             SAS::buildMenu
  852. menu::buildProc sasHelp             SAS::buildHelpMenu
  853. menu::buildProc sasKeywords         SAS::buildKeywordsMenu
  854. menu::buildProc markSasFileAs…      SAS::buildMarkMenu
  855.  
  856. # First build the main SAS menu.
  857.  
  858. proc SAS::buildMenu {} {
  859.     
  860.     global sasMenu
  861.     
  862.     set menuList [list                                  \
  863.       "sasHomePage"                                     \
  864.       "/S<U<OswitchToSas"                               \
  865.       [list Menu -n sasHelp             -M SAS {}]      \
  866.       "(-"                                              \
  867.       [list Menu -n sasKeywords         -M SAS {}]      \
  868.       [list Menu -n markSasFileAs…      -M SAS {}]      \
  869.       "(-"                                              \
  870.       "/P<U<OprocessFile"                               \
  871.       "/P<U<O<BprocessSelection"                        \
  872.       "(-"                                              \
  873.       "/I<U<OinsertPath"                                \
  874.       "(-"                                              \
  875.       "/N<U<BnextCommand"                               \
  876.       "/P<U<BprevCommand"                               \
  877.       "/S<U<BselectCommand"                             \
  878.       "/I<B<OreformatCommand"                           \
  879.       ]
  880.     set submenus [list markSasFileAs… sasHelp sasKeywords]
  881.     return       [list build $menuList SAS::menuProc $submenus $sasMenu]
  882. }
  883.  
  884. # Then build the "SAS Help" submenu.
  885.  
  886. proc SAS::buildHelpMenu {} {
  887.     
  888.     global SASmodeVars SASPrefsInMenu alpha::platform
  889.     
  890.     # Determine which key should be used for "Help", with F8 as option.
  891.     
  892.     if {!$SASmodeVars(noHelpKey)} {
  893.         set key "/t"
  894.     } else {
  895.         set key "/l"
  896.     } 
  897.     
  898.     # Reverse the local, www key bindings depending on the value of the
  899.     # 'Local Help" variable.
  900.     
  901.     if {!$SASmodeVars(localHelp)} {
  902.         set menuList [list                  \
  903.           "${key}<OwwwCommandHelp…"         \
  904.           "${key}<IlocalCommandHelp…"       \
  905.           ]
  906.     } else {
  907.         set menuList [list                  \
  908.           "${key}<OlocalCommandHelp…"       \
  909.           "${key}<IwwwCommandHelp…"         \
  910.           ]
  911.     } 
  912.     lappend menuList "(-"
  913.     if {${alpha::platform} == "alpha"} {
  914.         set prefix "!√"
  915.     } else {
  916.         set prefix "!•"
  917.     } 
  918.     foreach pref $SASPrefsInMenu {
  919.         if {$SASmodeVars($pref)} {
  920.             lappend menuList "${prefix}$pref"
  921.         } else {
  922.             lappend menuList "$pref"
  923.         }
  924.     }
  925.     lappend menuList "(-"
  926.     lappend menuList "checkKeywords"
  927.     lappend menuList "addNewCommands"
  928.     lappend menuList "addNewArguments"
  929.     lappend menuList "setSasApplication"
  930.     lappend menuList "(-"
  931.     lappend menuList "${key}<BsasModeHelp"
  932.     
  933.     return [list build $menuList SAS::helpProc {}]
  934. }
  935.  
  936. # Then build the "SAS Mode Keywords" submenu.
  937.  
  938. proc SAS::buildKeywordsMenu {} {
  939.     
  940.     set menuList [list                  \
  941.       "listKeywords"                    \
  942.       "checkKeywords"                   \
  943.       "addNewCommands"                  \
  944.       "addNewArguments"                 \
  945.       ]
  946.     return [list build $menuList SAS::keywordsProc {}]
  947. }
  948.  
  949. # Then build the "Mark SAS File As" submenu.
  950.  
  951. proc SAS::buildMarkMenu {} {
  952.     
  953.     global SASmodeVars alpha::platform
  954.     
  955.     set menuList [list                  \
  956.       "source"                          \
  957.       "output"                          \
  958.       "(-"                              \
  959.       ]
  960.     if {${alpha::platform} == "alpha"} {
  961.         set prefix "!√"
  962.     } else {
  963.         set prefix "!•"
  964.     } 
  965.     if {$SASmodeVars(autoMark)} {
  966.         lappend menuList "${prefix}autoMark"
  967.     } else {
  968.         lappend menuList "autoMark"
  969.     }
  970.     return [list build $menuList SAS::markFileProc {}]
  971. }
  972.  
  973. proc SAS::rebuildMenu {{menuName "sasMenu"} {pref ""}} {
  974.     menu::buildSome $menuName
  975. }
  976.  
  977. # Dim some menu items when there are no open windows.
  978. set menuItems {
  979.     processFile processSelection markSasFileAs
  980.     insertPath 
  981.     nextCommand prevCommand selectCommand
  982. }
  983. foreach i $menuItems {
  984.     hook::register requireOpenWindowsHook [list sasMenu $i] 1
  985. unset i menuItems 
  986.  
  987. # Now we actually build the SAS menu.
  988.  
  989. menu::buildSome sasMenu
  990.  
  991.  
  992. # ===========================================================================
  993. # ◊◊◊◊ SAS menu support ◊◊◊◊ #
  994.  
  995. # This is the procedure called for all main menu items.
  996.  
  997. proc SAS::menuProc {menu item} {
  998.     SAS::$item
  999. }
  1000.  
  1001. # Give a beta message for untested features / menu items.
  1002.  
  1003. proc SAS::betaMessage {{kill 1}} {
  1004.     
  1005.     beep ; message "Sorry, this feature has not been fully implemented."
  1006.     if {$kill} {return -code return}
  1007. }
  1008.  
  1009. # ===========================================================================
  1010. # Open the SAS home page.
  1011.  
  1012. proc SAS::sasHomePage {} {
  1013.  
  1014.     global SASmodeVars
  1015.     
  1016.     url::execute $SASmodeVars(sasHomePage)
  1017. }
  1018.  
  1019. # ===========================================================================
  1020. # Switch to SAS application
  1021.  
  1022. proc SAS::switchToSas {} {app::launchFore [SAS::sig]}
  1023.  
  1024. # ===========================================================================
  1025. # Return the SAS signature.
  1026.  
  1027. proc SAS::sig {{app "SAS"}} {
  1028.     
  1029.     global SASmodeVars
  1030.     
  1031.     set lowApp [string tolower $app]
  1032.     set capApp [string toupper $app]
  1033.     if {$SASmodeVars(${lowApp}Sig) == ""} {
  1034.         alertnote "Looking for the $capApp application ..."
  1035.         SAS::setApplication $lowApp
  1036.     }
  1037.     return $SASmodeVars(${lowApp}Sig)
  1038. }
  1039.  
  1040. # ===========================================================================
  1041. # Set Application
  1042. # Prompt the user to locate the local SAS application.
  1043.  
  1044. proc SAS::setApplication {{app "SAS"}} {
  1045.     
  1046.     global mode SASmodeVars
  1047.     
  1048.     set lowApp [string tolower $app]
  1049.     set capApp [string toupper $app]
  1050.     
  1051.     set newSig ""
  1052.     set newSig [dialog::askFindApp $capApp $SASmodeVars(${lowApp}Sig)]
  1053.     
  1054.     if {$newSig != ""} {
  1055.         set SASmodeVars(${lowApp}Sig) "$newSig"
  1056.         set oldMode $mode
  1057.         set mode "SAS"
  1058.         synchroniseModeVar "${lowApp}Sig" $SASmodeVars(${lowApp}Sig)
  1059.         set mode $oldMode
  1060.         message "The $capApp signature has been changed to \"$newSig\"."
  1061.     } else {
  1062.         message "Cancelled."
  1063.     }
  1064. }
  1065.  
  1066. # ===========================================================================
  1067. # ◊◊◊◊ Help ◊◊◊◊ #
  1068.  
  1069. proc SAS::helpProc {menu item} {
  1070.  
  1071.     global SASmodeVars SASPrefsInMenu
  1072.     
  1073.     if {$item == "wwwCommandHelp"} {
  1074.         SAS::wwwCommandHelp
  1075.     } elseif  {$item == "localCommandHelp"} {
  1076.         SAS::localCommandHelp
  1077.     } elseif {[lsearch -exact $SASPrefsInMenu $item] != -1} {
  1078.         SAS::flagFlip $item
  1079.         SAS::rebuildMenu sasHelp
  1080.     } elseif {$item == "setSasApplication"} {
  1081.         SAS::selectApplication "SAS"
  1082.     } elseif {$item == "sasModeHelp"} {
  1083.         package::helpFile "SAS"
  1084.     } else {
  1085.         SAS::$item
  1086.     } 
  1087. }
  1088.  
  1089. # ===========================================================================
  1090. # ◊◊◊◊ Keywords ◊◊◊◊ #
  1091.  
  1092. proc SAS::keywordsProc {menuName item} {
  1093.  
  1094.     global SASlowerCaseCmds
  1095.     
  1096.     if {$item == "listKeywords"} {
  1097.         set keywords [listpick -l -p "Current SAS mode keywords…" $SASlowerCaseCmds]
  1098.         foreach keyword $keywords {
  1099.             SAS::checkKeywords $keyword
  1100.         }
  1101.     } elseif {$item == "addNewCommands" || $item == "addNewArguments"} {
  1102.         set item [string trimleft $item "addNew"]
  1103.         if {$item == "Commands" && [llength [winNames]] && [askyesno \
  1104.           "Would you like to add all of the \"extra\" commands from this window\
  1105.           to the \"Add Commands\" preference?"] == "yes"} {
  1106.             SAS::addWindowCommands
  1107.         } else {
  1108.             SAS::addKeywords $item
  1109.         }
  1110.     } else {
  1111.         SAS::$item
  1112.     } 
  1113. }
  1114.  
  1115. # ===========================================================================
  1116. # SAS::addWindowCommands
  1117. # Add all of the "extra" commands which appear in entries in this window.
  1118.  
  1119. proc SAS::addWindowCommands {} {
  1120.     
  1121.     global mode SAScmds SASmodeVars
  1122.     
  1123.     if {![llength [winNames]]} {
  1124.         message "Cancelled -- no current window!"
  1125.         return
  1126.     } 
  1127.     
  1128.     message "Scanning [win::CurrentTail] for all commands…"
  1129.     
  1130.     set pos [minPos]
  1131.     set pat {^([a-zA-Z0-9]+[a-zA-Z0-9])+[\t ]}
  1132.     while {![catch {search -f 1 -r 1 $pat $pos} match]} {
  1133.         set pos [nextLineStart [lindex $match 1]]
  1134.         set commandLine [getText [lindex $match 0] [lindex $match 1]]
  1135.         regexp $pat $commandLine match aCommand
  1136.         set aCommand [string tolower $aCommand]
  1137.         if {![lcontains SAScmds $aCommand]} {
  1138.             append SASmodeVars(addCommands) " $aCommand"
  1139.         } 
  1140.     }
  1141.     set SASmodeVars(addCommands) [lsort [lunique $SASmodeVars(addCommands)]]
  1142.     set oldMode $mode
  1143.     set mode "SAS"
  1144.     synchroniseModeVar addCommands $SASmodeVars(addCommands)
  1145.     set mode $oldMode
  1146.     if {[llength $SASmodeVars(addCommands)]} {
  1147.         SAS::colorizeSAS
  1148.         listpick -p "The \"Add Commands\" preference includes:" \
  1149.           $SASmodeVars(addCommands)
  1150.         message "Use the \"Mode Prefs --> Preferences\" menu item to edit keyword lists."
  1151.     } else {
  1152.         message "No \"extra\" commands from this window were found."
  1153.     } 
  1154. }
  1155.  
  1156. proc SAS::addKeywords {{category} {keywords ""}} {
  1157.     
  1158.     global mode SASmodeVars    
  1159.     
  1160.     if {$keywords == ""} {
  1161.         set keywords [prompt "Enter new SAS $category:" ""]
  1162.     }
  1163.     
  1164.     # The list of keywords should all be lower case.
  1165.     set keywords [string tolower $keywords]
  1166.     # Check to see if the keyword is already defined.
  1167.     foreach keyword $keywords {
  1168.         set checkStatus [Lisp::checkKeywords $keyword 1 0]
  1169.         if {$checkStatus != "0"} {
  1170.             alertnote "Sorry, \"$keyword\" is already defined\
  1171.               in the $checkStatus list."
  1172.             message "Cancelled."
  1173.             return -code return
  1174.         } 
  1175.     }
  1176.     # Keywords are all new, so add them to the appropriate mode preference.
  1177.     append SASmodeVars(add$category) " $keywords"
  1178.     set SASmodeVars(add$category) [lsort $SASmodeVars(add$category)]
  1179.     set oldMode $mode
  1180.     set mode "SAS"
  1181.     synchroniseModeVar add$category $SASmodeVars(add$category)
  1182.     set mode $oldMode
  1183.     SAS::colorizeSAS
  1184.     message "\"$keywords\" added to $category preference."
  1185. }
  1186.  
  1187. proc SAS::checkKeywords {{newKeywordList ""} {quietly 0} {noPrefs 0}} {
  1188.     
  1189.     global SASmodeVars
  1190.     
  1191.     global SASCommands  SASUserCommands  SASSubprocs     
  1192.     global SASArguments SASUserArguments SASOptions   SASParameters  
  1193.     
  1194.     set type 0
  1195.     if {$newKeywordList == ""} {
  1196.         set quietly 0
  1197.         set newKeywordList [prompt "Enter SAS mode keywords to be checked:" ""]
  1198.     }
  1199.     # Check to see if the new keyword(s) is already defined.
  1200.     foreach newKeyword $newKeywordList {
  1201.         if {[lsearch -exact $SASCommands $newKeyword] != "-1"} {
  1202.             set type SASCommands
  1203.         } elseif {[lsearch -exact $SASUserCommands $newKeyword] != "-1"} {
  1204.             set type SASUserCommands
  1205.         } elseif {[lsearch -exact $SASSubprocs $newKeyword] != "-1"} {
  1206.             set type SASSubprocs
  1207.         } elseif {[lsearch -exact $SASArguments $newKeyword] != "-1"} {
  1208.             set type SASArguments
  1209.         } elseif {[lsearch -exact $SASUserArguments $newKeyword] != "-1"} {
  1210.             set type SASUserArguments
  1211.         } elseif {[lsearch -exact $SASOptions $newKeyword] != "-1"} {
  1212.             set type SASOptions
  1213.         } elseif {[lsearch -exact $SASParameters $newKeyword] != "-1"} {
  1214.             set type SASParameters
  1215.         } elseif {!$noPrefs && \
  1216.           [lsearch -exact $SASmodeVars(addCommands) $newKeyword] != "-1"} {
  1217.             set type SASmodeVars(addCommands)
  1218.         } elseif {!$noPrefs && \
  1219.           [lsearch -exact $SASmodeVars(addArguments) $newKeyword] != "-1"} {
  1220.             set type SASmodeVars(addArguments)
  1221.         }
  1222.         if {$quietly} {
  1223.             # When this is called from other code, it should only contain
  1224.             # one keyword to be checked, and we'll return it's type.
  1225.             return "$type"
  1226.         } elseif {!$quietly && $type == 0} {
  1227.             alertnote "\"$newKeyword\" is not currently defined\
  1228.               as a SAS mode keyword"
  1229.         } elseif {$type != 0} {
  1230.             # This will work for any other value for "quietly", such as "2"
  1231.             alertnote "\"$newKeyword\" is currently defined as a keyword\
  1232.               in the \"$type\" list."
  1233.         } 
  1234.         set type 0
  1235.     }
  1236. }
  1237.  
  1238. # ===========================================================================
  1239. # ◊◊◊◊ Marks ◊◊◊◊ #
  1240.  
  1241. proc SAS::markFileProc {menu item} {
  1242.  
  1243.     if {$item == "source"} {
  1244.         SAS::MarkFile {.sas}
  1245.     } elseif {$item == "output"} {
  1246.         # doesn't really matter what we put for the mark file "type" here,
  1247.         # since output is the default if other "if ..." cases aren't met.
  1248.         SAS::MarkFile {.out}
  1249.     } elseif {$item == "autoMark"} {
  1250.         SAS::flagFlip autoMark
  1251.         SAS::rebuildMenu markSasFileAs…
  1252.     }
  1253. }
  1254.  
  1255. # ===========================================================================
  1256. # ◊◊◊◊ Processing ◊◊◊◊ #
  1257.  
  1258. # ===========================================================================
  1259. # Process File
  1260.  
  1261. # Send entire file to SAS for processing, adding carriage return at end
  1262. # of file if necessary.
  1263. # Optional "f" argument allows this to be called by other code, or to be 
  1264. # sent via a Tcl shell window.
  1265.  
  1266. proc SAS::processFile {{f ""} {app "SAS"}} {
  1267.     
  1268.     if {$f != ""} {file::openAny $f}
  1269.     set f [win::Current]
  1270.  
  1271.     set dirtyWindow [winDirty]
  1272.     set dontSave 0
  1273.     if {$dirtyWindow && [askyesno \
  1274.       "Do you want to save the file before sending it to SAS?"] == "yes"} {
  1275.         save
  1276.     } else {
  1277.         set dontSave 1
  1278.     } 
  1279.     if {!$dontSave && [lookAt [pos::math [maxPos] - 1]] != "\r"} {
  1280.         set pos [getPos]
  1281.         goto [maxPos]
  1282.         insertText "\r"
  1283.         goto $pos
  1284.         alertnote "Carriage return added to end of file."
  1285.         save
  1286.     }
  1287.  
  1288.     app::launchBack '[SAS::sig]'
  1289.     sendOpenEvent noReply '[SAS::sig]' $f
  1290.     switchTo '[SAS::sig]'
  1291. }
  1292.  
  1293. # ===========================================================================
  1294. # Process Selection
  1295. # Procedure to implement transfer of selected lines to SAS for processing.
  1296.  
  1297. proc SAS::processSelection {{selection ""} {app "SAS"}} {
  1298.     
  1299.     global PREFS
  1300.     
  1301.     if {$selection == ""} {
  1302.         if {![isSelection]} {
  1303.             message "No selection -- cancelled."
  1304.             return
  1305.         } else {
  1306.             set selection [getSelect]
  1307.         } 
  1308.     }
  1309.     file::ensureDirExists [file join $PREFS SAS-tmp]
  1310.     set newFile [file join $PREFS SAS-tmp temp-SAS.do]
  1311.     file::writeAll $newFile $selection 1
  1312.  
  1313.     app::launchBack '[SAS::sig]'
  1314.     sendOpenEvent noReply '[SAS::sig]' $newFile
  1315.     switchTo '[SAS::sig]'
  1316. }
  1317.  
  1318. proc SAS::quitHook {} {temp::cleanup SAS-tmp}
  1319.  
  1320. # ===========================================================================
  1321. # ◊◊◊◊ Insertions ◊◊◊◊ #
  1322.  
  1323. proc SAS::insertPath {} {
  1324.     
  1325.     global file::separator
  1326.     
  1327.     set path ""
  1328.     set t    ""
  1329.     append t "\"${file::separator}"
  1330.     set path [getfile "Choose path of target file:"]
  1331.     if {$path != ""} {
  1332.         append t $path
  1333.         append t "\""
  1334.         insertText $t
  1335.     }
  1336. }
  1337.  
  1338. # ===========================================================================
  1339. # ◊◊◊◊ Navigation ◊◊◊◊ #
  1340.  
  1341. # Next/Prev command can simply return the position of the next command
  1342. # (quietly == 1), move the cursor to the next command (placing the cursor
  1343. # at the top of the window if toTop == 1), extend the current selection to
  1344. # the end of the this command, or (if the current command is already
  1345. # highlighted in its entirety) extend the current selection to the end of
  1346. # the next command.
  1347.  
  1348. proc SAS::nextCommand {{quietly 0} {toTop 0}} {
  1349.     
  1350.     if {[pos::compare [selEnd] == [maxPos]]} {
  1351.         set pos [maxPos]
  1352.     } else {
  1353.         set pos [pos::math [selEnd] + 1]
  1354.     } 
  1355.     set pat {^[^\r\n\t \*/]}
  1356.  
  1357.     if {![catch {search -f 1 -r 1 $pat $pos} match]} {
  1358.         set pos [lineStart [lindex $match 1]]
  1359.     } else {
  1360.         set pos [maxPos]
  1361.     }
  1362.     if {$quietly} {
  1363.         return $pos
  1364.     } elseif {[isSelection]} {
  1365.     set limit1 [lindex [SAS::getCommand [selEnd]] 1]
  1366.     set limit2 [lindex [SAS::getCommand $pos    ] 1]
  1367.     if {$limit2 == "-1"} {set limit2 [maxPos]}
  1368.     if {$limit1 == "-1"} {set limit1 $limit2}
  1369.     if {[pos::compare [selEnd] < $limit1]} {
  1370.         select [getPos] $limit1
  1371.     } else {
  1372.         select [getPos] $limit2
  1373.     } 
  1374.     } elseif {$pos == [maxPos]} {
  1375.     message "No further commands in the file."
  1376.     return
  1377.     } else {
  1378.         goto $pos
  1379.         message [getText $pos [nextLineStart $pos]]
  1380.     } 
  1381.     if {$toTop} {insertToTop}
  1382. }
  1383.  
  1384. proc SAS::prevCommand {{quietly 0} {toTop 0}} {
  1385.     
  1386.     if {[pos::compare [getPos] == [minPos]]} {
  1387.         set pos [minPos]
  1388.     } else {
  1389.         set pos [pos::math [getPos] - 1]
  1390.     } 
  1391.     set pat {^[^\r\n\t \*/]}
  1392.  
  1393.     if {![catch {search -f 0 -r 1 $pat $pos} match]} {
  1394.         set pos [lineStart [lindex $match 1]]
  1395.     } else {
  1396.         set pos [minPos]
  1397.     }
  1398.     if {$quietly} {
  1399.         return $pos
  1400.     } elseif {[isSelection]} {
  1401.     # Going backwards is actually easier with selections.
  1402.     select $pos [selEnd]
  1403.     } elseif {$pos == [minPos]} {
  1404.         message "No further commands in the file."
  1405.         return
  1406.     } else {
  1407.         goto $pos
  1408.         message [getText $pos [nextLineStart $pos]]
  1409.     } 
  1410.     if {$toTop} {insertToTop}
  1411.     return $pos
  1412. }
  1413.  
  1414. proc SAS::searchFunc {direction} {
  1415.     
  1416.     if {$direction} {
  1417.         SAS::nextCommand
  1418.     } else {
  1419.         SAS::prevCommand
  1420.     }
  1421. }
  1422.  
  1423. proc SAS::selectCommand {} {
  1424.     
  1425.     set pos    [getPos]
  1426.     set limits [SAS::getCommand $pos]
  1427.     set posBeg [lindex $limits 0]
  1428.     set posEnd [lindex $limits 1]
  1429.     
  1430.     if {$posBeg != "-1" && $posEnd != "-1" && \
  1431.       [pos::compare $pos >= $posBeg] && [pos::compare $pos <= $posEnd]} {
  1432.         select $posBeg $posEnd
  1433.     } else {
  1434.         message "The cursor is not within a command."
  1435.         error "The cursor is not within a command."
  1436.     } 
  1437. }
  1438.  
  1439. proc SAS::copyCommand {{quietly 0}} {
  1440.     
  1441.     set pos [getPos]
  1442.     if {[set posBeg [lindex [SAS::getCommand $pos] 0]] != "-1"} {
  1443.         goto $posBeg
  1444.         forwardWord
  1445.         set posEnd [getPos]
  1446.         if {!$quietly} {
  1447.             select $posBeg $posEnd
  1448.             copy
  1449.             message "\"[getText $posBeg $posEnd]\" copied to clipboard."
  1450.         } 
  1451.         goto $pos
  1452.         return [getText $posBeg $posEnd]
  1453.     } elseif {!$quietly} {
  1454.         message "The cursor is not within a command."
  1455.     }
  1456.     return ""
  1457. }
  1458.  
  1459. proc SAS::reformatCommand {} {
  1460.     
  1461.     if {![isSelection]} {SAS::selectCommand} 
  1462.     message "Reformatting …"
  1463.     ::indentRegion
  1464.     goto [pos::math [getPos] -1]
  1465.     goto [SAS::nextCommand 1]
  1466.     message "Reformatted."
  1467. }
  1468.  
  1469. proc SAS::getCommand {pos} {
  1470.     
  1471.     set pos1 [pos::math [nextLineStart $pos] - 1]
  1472.     set pat {^[^\r\n\t \}\)]}
  1473.     set posBeg "-1"
  1474.     set posEnd "-1"
  1475.     if {![catch {search -f 0 -r 1 $pat $pos1} match]} {
  1476.         set posBeg [lindex $match 0]
  1477.         set pos2   [nextLineStart $posBeg]
  1478.         if {![catch {search -f 1 -r 1 $pat $pos2} match]} {
  1479.             set posEnd [lindex $match 0]
  1480.         } else {
  1481.             set posEnd [maxPos]
  1482.         } 
  1483.         # Now back up to remove empty or commented lines.
  1484.         set posEndPrev [pos::math $posEnd - 1]
  1485.         set prevLine [getText [lineStart $posEndPrev] $posEndPrev]
  1486.         while {[regexp {^[\t ]*$} $prevLine]} {
  1487.             set posEnd [lineStart $posEndPrev]
  1488.             set posEndPrev [pos::math $posEnd - 1]
  1489.             set prevLine [getText [lineStart $posEndPrev] $posEndPrev]
  1490.         }
  1491.     } 
  1492.     return [list $posBeg $posEnd]
  1493. }
  1494.  
  1495. # ===========================================================================
  1496. # ◊◊◊◊ --------------------- ◊◊◊◊ #
  1497. # ◊◊◊◊ version history ◊◊◊◊ #
  1498. #  modified by  vers#  reason
  1499. #  -------- --- ------ -----------
  1500. #  01/28/20 cbu 1.0.1  First created sas mode, based upon other modes found 
  1501. #                        in Alpha's distribution.  Commands are based on 
  1502. #                        version 2.0.1 of SAS.
  1503. #  03/02/20 cbu 1.0.2  Minor modifications to comment handling.
  1504. #  03/20/00 cbu 1.0.3  Minor update of keywords dictionaries.
  1505. #                      Renamed mode SAS, from sas 
  1506. #  04/01/00 cbu 1.0.4  Fixed a little bug with "comment box".
  1507. #                      Added new preferences to allow the user to enter 
  1508. #                        additional commands and options.  
  1509. #                      Reduced the number of different user-specified colors.
  1510. #                      Added "Update Colors" proc to avoid need for a restart
  1511. #  04/08/00 cbu 1.0.5  Unset obsolete preferences from earlier versions.
  1512. #                      Modified "Electric Semi" added "Continue Comment" and
  1513. #                        "Electric Return Over-ride".
  1514. #                      Renamed "Update Colors" to "Update Preferences".
  1515. #  04/16/00 cbu 1.1    Renamed to sasMode.tcl
  1516. #                      Added "Mark File" and "Parse Functions" procs.
  1517. #  06/22/00 cbu 1.2    "Mark File" now recognizes headings as well as commands.
  1518. #                      "Mark File" recognizes source or output files.
  1519. #                      Completions, Completions Tutorial added.
  1520. #                      "Reload Completions", referenced by "Update Preferences".
  1521. #                      Better support for user defined keywords.
  1522. #                      Removed "Continue Comment", now global in Alpha 7.4.
  1523. #                      Added command double-click for on-line help.
  1524. #                      <shift, control>-<command> double-click syntax info.
  1525. #                        (Foundations, at least.  Ongoing project.)
  1526. #  06/22/00 cbu 1.2.1  "Mark File"ignores "run" commands.
  1527. #                      Minor keywords update.
  1528. #                      Beta-version of a SAS menu, based on the Stata menu.
  1529. #                      Added "sasSig" preference to allow user to find
  1530. #                        local application if necessary.
  1531. #                      Added SAS::sig which returns SAS signature.
  1532. #  08/28/00 cbu 1.2.2  Added some of the flag preferences to "SAS Help" menu.
  1533. #                      Added "flagFlip" to update preference bullets in menu.
  1534. #                      Added a "noHelpKey" preference, which switches the
  1535. #                        "help" key binding to F8.
  1536. #                      Added "Add New Commands / Arguments" to "SAS Help" menu.
  1537. #                      Added "Set SAS Application to "SAS Help" menu.
  1538. #  11/05/00 cbu 1.3    Added "next/prevCommand", "selectCommand", and
  1539. #                        "copyCommand" procs to menu.
  1540. #                      Added "SAS::indentLine".
  1541. #                      Added "SAS::reformatCommand" to menu.
  1542. #                      "SAS::reloadCompletions" is now obsolete.
  1543. #                      "SAS::updatePreferences" is now obsolete.
  1544. #                      "SAS::colorizeSAS" now takes care of setting all 
  1545. #                        keyword lists, including SAScmds.
  1546. #                      Cleaned up completion procs.  This file never has to be
  1547. #                        reloaded.  (Similar cleaning up for "SAS::DblClick").
  1548. #  11/16/00 cbu 2.0    New url prefs handling requires 7.4b21
  1549. #                      Added "Home Page" pref, menu item.
  1550. #                      Removed  hook::register requireOpenWindowsHook from
  1551. #                        mode declaration, put it after menu build.
  1552. #  12/19/00 cbu 2.1    The menu proc "Add Commands" now includes an option
  1553. #                        to grab all of the "extra" command from the current
  1554. #                        window, using SAS::addWindowCommands.
  1555. #                      Added "Keywords" submenu, "List Keywords" menu item.
  1556. #                      Big cleanup of ::sig, ::setApplication, processing ...
  1557. #  01/25/01 cbu 2.1.1  Bug fix for SAS::processSelection/File.
  1558. #                      Bug fix for comment characters.
  1559. #
  1560.  
  1561. # ===========================================================================
  1562. # .
  1563.